﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License, as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Diagnostics;
using ExtremeML.Core;

namespace ExtremeML.Spreadsheet.Address
{
	/// <summary>
	/// Represents the grid addressing for a single worksheet cell.
	/// </summary>
	public class CellAddress : IAddressDependency, IWrapper, IComparable<CellAddress>
	{
		#region Housekeeping

		/// <summary>
		/// Initializes a new instance of the <see cref="CellAddress"/> class.
		/// </summary>
		internal CellAddress()
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CellAddress"/> class.
		/// </summary>
		/// <param name="address">The grid address of a cell.</param>
		internal CellAddress(
			GridReference address)
			: this(address, false, false)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="CellAddress"/> class.
		/// </summary>
		/// <param name="address">The grid address of a cell.</param>
		/// <param name="rowAbsolute">A value that indicates whether the row coordinate is absolute.</param>
		/// <param name="columnAbsolute">A value that indicates whether the column coordinate is absolute.</param>
		internal CellAddress(
			GridReference address,
			bool rowAbsolute,
			bool columnAbsolute)
		{
			RowIndex = address.RowIndex;
			ColumnIndex = address.ColumnIndex;
			RowAbsolute = rowAbsolute;
			ColumnAbsolute = columnAbsolute;
		}

		#endregion
		#region Properties

		/// <summary>
		/// Gets or sets the zero-based row index.
		/// </summary>
		public int? RowIndex { get; internal set; }

		/// <summary>
		/// Gets or sets the zero-based column index.
		/// </summary>
		public int? ColumnIndex { get; internal set; }

		/// <summary>
		/// Gets whether the row coordinate is absolute.
		/// </summary>
		public bool? RowAbsolute { get; internal set; }

		/// <summary>
		/// Gets whether the column coordinate is absolute.
		/// </summary>
		public bool? ColumnAbsolute { get; internal set; }

		/// <summary>
		/// Gets the cell reference.
		/// </summary>
		public string Reference
		{
			get
			{
				if (!IsPopulated()) return null;

				return (ColumnAbsolute.Value ? "$" : string.Empty) +
					AddressParser.EncodeColumnName(ColumnIndex.Value) +
					(RowAbsolute.Value ? "$" : string.Empty) +
					(RowIndex.Value + 1);
			}
		}

		#endregion
		#region Interface Implementation
		#region IAddressDependency

		/// <summary>
		/// Accepts notification that the specified number of empty
		/// rows have been inserted before the specified row index.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the row that follows the insertion point.</param>
		/// <param name="rowCount">The number of rows inserted.</param>
		public void NotifyRowInsertion(
			int rowIndex,
			int rowCount)
		{
			if (IsPopulated())
			{
				if (RowIndex >= rowIndex)
				{
					// Dependent item is displaced by the insertion
					RowIndex += rowCount;
				}
			}
		}

		/// <summary>
		/// Accepts notification that the specified number of rows
		/// have been deleted, starting at the specified row index.
		/// </summary>
		/// <param name="rowIndex">The zero-based index of the first row deleted.</param>
		/// <param name="rowCount">The number of rows deleted.</param>
		public void NotifyRowDeletion(
			int rowIndex,
			int rowCount)
		{
			if (IsPopulated())
			{
				if (RowIndex >= rowIndex)
				{
					if (RowIndex >= rowIndex + rowCount)
					{
						// Dependent item is displaced by the deletion
						RowIndex -= rowCount;
					}
					else
					{
						// Dependent item is deleted by the deletion
						if (CellDeleted != null)
						{
							CellDeleted(this, EventArgs.Empty);
						}
					}
				}
			}
		}

		/// <summary>
		/// Accepts notification that the specified number of empty
		/// columns have been inserted before the specified column index.
		/// </summary>
		/// <param name="columnIndex">The zero-based index of the column that follows the insertion point.</param>
		/// <param name="columnCount">The number of columns inserted.</param>
		public void NotifyColumnInsertion(
			int columnIndex,
			int columnCount)
		{
			if (IsPopulated())
			{
				if (ColumnIndex >= columnIndex)
				{
					// Dependent item is displaced by the insertion
					ColumnIndex += columnCount;
				}
			}
		}

		/// <summary>
		/// Accepts notification that the specified number of columns
		/// have been deleted, starting at the specified column index.
		/// </summary>
		/// <param name="columnIndex">The zero-based index of the column that follows the insertion point.</param>
		/// <param name="columnCount">The number of columns inserted.</param>
		public void NotifyColumnDeletion(
			int columnIndex,
			int columnCount)
		{
			if (IsPopulated())
			{
				if (ColumnIndex >= columnIndex)
				{
					if (ColumnIndex >= columnIndex + columnCount)
					{
						// Dependent item is displaced by the deletion
						ColumnIndex -= columnCount;
					}
					else
					{
						// Dependent item is deleted by the deletion
						if (CellDeleted != null)
						{
							CellDeleted(this, EventArgs.Empty);
						}
					}
				}
			}
		}

		#endregion
		#region IWrapper

		/// <summary>
		/// Returns a value that indicates whether this instance wraps content that should be saved to the document.
		/// </summary>
		public bool IsPopulated()
		{
			return RowIndex.HasValue && ColumnIndex.HasValue && RowAbsolute.HasValue && ColumnAbsolute.HasValue;
		}

		/// <summary>
		/// Establishes relationships with other package content objects.
		/// </summary>
		public void Activate()
		{
			// This class requires no activation.		
		}

		/// <summary>
		/// Safely terminates relationships with other package content objects.
		/// </summary>
		public void Deactivate()
		{
			// This class requires no deactivation.		
		}

		#endregion
		#region IComparable<T>

		/// <summary>
		/// Implements interface <see cref="IComparable{T}"/>
		/// </summary>
		/// <param name="other">A <see cref="CellAddress"/> to compare with this instance.</param>
		/// <returns>A 32-bit signed integer that indicates the relative order of the objects being compared.</returns>
		public int CompareTo(
			CellAddress other)
		{
			if (RowIndex > other.RowIndex)
			{
				// This instance is greater than other 
				return 1;
			}
			else if (RowIndex < other.RowIndex)
			{
				// This instance is less than other 
				return -1;
			}
			else
			{
				// Compare column addressing
				if (ColumnIndex > other.ColumnIndex)
				{
					// This instance is greater than other 
					return 1;
				}
				else if (ColumnIndex < other.ColumnIndex)
				{
					// This instance is less than other 
					return -1;
				}
				else
				{
					// The two instances are equal
					return 0;
				}
			}
		}

		#endregion
		#endregion
		#region Overrides

		/// <summary>
		/// Overrides the base class to return the value of the <see cref="Reference"/> property.
		/// </summary>
		/// <returns>A <see cref="string"/> that represents the value of this instance.</returns>
		public override string ToString()
		{
			return Reference;
		}

		/// <summary>
		/// Overrides the equality operator to compare the values of two <see cref="CellAddress"/> instances.
		/// </summary>
		/// <param name="a">A <see cref="CellAddress"/> instance to compare.</param>
		/// <param name="b">A <see cref="CellAddress"/> instance to compare.</param>
		/// <returns>A value that indicates whether the passed instances are equal.</returns>
		public static bool operator ==(
			CellAddress a,
			CellAddress b)
		{
			return
				a.IsPopulated() &&
				b.IsPopulated() &&
				a.RowIndex.Value == b.RowIndex.Value &&
				a.ColumnIndex.Value == b.ColumnIndex.Value;
		}

		/// <summary>
		/// Overrides the inequality operator to compare the values of two <see cref="CellAddress"/> instances.
		/// </summary>
		/// <param name="a">A <see cref="CellAddress"/> instance to compare.</param>
		/// <param name="b">A <see cref="CellAddress"/> instance to compare.</param>
		/// <returns>A value that indicates whether the passed instances are not equal.</returns>
		public static bool operator != (
			CellAddress a,
			CellAddress b)
		{
			return !(a == b);
		}

		#endregion
		#region Events

		/// <summary>
		/// Occurs when the this cell is deleted by a worksheet row or column deletion operation.
		/// </summary>
		internal event EventHandler CellDeleted;

		#endregion
	}
}
